package org.retabel;

import org.apache.commons.io.IOUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
import java.util.List;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * Utility class providing an easy to use api for creating a zip archive
 * <p/>
 * <h3>Extra-Info</h3>
 * Created: 23:27 20.03.12
 *
 * @author Peter Herber
 * @version 1.0
 */
public class ZipArchiveBuilder {

    private class ZipArchiveBuilderEntry {

        private final String resourceNameInZip;
        private final String resource;
        private final Class<?> relativeToClass;

        private ZipArchiveBuilderEntry(String resourceNameInZip, String resource, Class<?> relativeToClass) {
            this.resource = resource;
            this.resourceNameInZip = resourceNameInZip;
            this.relativeToClass = relativeToClass;
        }

        public String getResource() {
            return resource;
        }

        public String getResourceNameInZip() {
            return resourceNameInZip;
        }

        public Class<?> getRelativeToClass() {
            return relativeToClass;
        }
    }

    /**
     * all zip archive builder entries are hold in this variable
     */
    private final List<ZipArchiveBuilderEntry> zipArchiveBuilderEntries;

    /**
     * Create a new ZipArchiveBuilder instance
     */
    public ZipArchiveBuilder() {
        zipArchiveBuilderEntries = new LinkedList<>();
    }

    /**
     * Adds a new resource into the resulting zip archive
     * @param resourceNameInZip the name of the zip archive entry
     * @param resource the resource within the class path
     * @return a new instance of ZipArchiveBuilder containing all resources including the new one
     */
    public void addResource(String resourceNameInZip, String resource) {
        ZipArchiveBuilderEntry zipArchiveBuilderEntry = new ZipArchiveBuilderEntry(resourceNameInZip, resource, null);
        zipArchiveBuilderEntries.add(zipArchiveBuilderEntry);
    }

    /**
     * Adds a new resource relying relative to the given class into the resulting zip archive
     * @param resourceNameInZip the name of the zip archive entry
     * @param resource the resource within the class path
     * @param relativeToClass the resource relative to this class in class path
     * @return a new instance of ZipArchiveBuilder containing all resources including the new one
     */
    public void addResource(String resourceNameInZip, String resource, Class relativeToClass) {
        ZipArchiveBuilderEntry zipArchiveBuilderEntry = new ZipArchiveBuilderEntry(resourceNameInZip, resource, relativeToClass);
        zipArchiveBuilderEntries.add(zipArchiveBuilderEntry);
    }

    /**
     * Packs all resources into a zip archive and returns the zip archive bytes
     * @return the packed zip archive bytes
     * @throws IOException if exception occurs
     */
    public byte[] toByteArray() throws IOException {
        ByteArrayOutputStream zipOutputStreamByteStream = new ByteArrayOutputStream();
        ZipOutputStream zipOutputStream = new ZipOutputStream(zipOutputStreamByteStream);
        zipOutputStream.setLevel(6);

        for (ZipArchiveBuilderEntry zipArchiveBuilderEntry : zipArchiveBuilderEntries)
        {
            CRC32 crc = new CRC32();

            InputStream resourceStream = null;
            if (zipArchiveBuilderEntry.getRelativeToClass() == null) {
                resourceStream = getClass().getClassLoader().getResourceAsStream(zipArchiveBuilderEntry.getResource());
            } else {
                resourceStream = zipArchiveBuilderEntry.getRelativeToClass().getResourceAsStream(zipArchiveBuilderEntry.getResource());
            }

            if (resourceStream == null) {
                throw new IOException("resource not found: "+zipArchiveBuilderEntry.getResource());
            }

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            IOUtils.copy(resourceStream, baos);
            resourceStream.close();

            byte[] bytes = baos.toByteArray();

            ZipEntry entry = new ZipEntry(zipArchiveBuilderEntry.getResourceNameInZip());
            entry.setSize((long) bytes.length);
            crc.reset();
            crc.update(bytes);
            entry.setCrc(crc.getValue());
            zipOutputStream.putNextEntry(entry);
            zipOutputStream.write(bytes, 0, bytes.length);
            zipOutputStream.closeEntry();
        }

        zipOutputStream.flush();
        zipOutputStream.close();

        return zipOutputStreamByteStream.toByteArray();
    }

}
